package data

import (
    "context"
    "errors"
    "fmt"
    "math"
    "time"

    "github.com/go-kratos/kratos/v2/log"
    "github.com/jinzhu/copier"
    "gorm.io/gorm"
    "gorm.io/gorm/clause"
    "mall/app/product/internal/biz"
    "mall/app/product/internal/domain"
)

var _ biz.ProductRepo = (*productRepo)(nil)

type Product struct {
    Id        uint      `gorm:"primaryKey;type:int(10) unsigned AUTO_INCREMENT;comment:自增主键"`
    Name      string    `gorm:"type:varchar(255) not null;default:'';comment:商品名称"`
    Price     float32   `gorm:"type:decimal(12,2) unsigned not null;default:0;comment:商品价格"`
    Stock     uint      `gorm:"type:int(10) unsigned not null;default:0;comment:商品库存"`
    CreatedAt time.Time `gorm:"type:datetime(0) not null;default:CURRENT_TIMESTAMP;comment:创建时间"`
}

type productRepo struct {
    data  *Data
    model *gorm.DB
    log   *log.Helper
}

func NewProductRepo(data *Data, logger log.Logger) biz.ProductRepo {
    return &productRepo{
        data:  data,
        model: data.db.Model(&Product{}),
        log:   log.NewHelper(log.With(logger, "module", "repo/product")),
    }
}

func (repo *productRepo) Create(ctx context.Context, product *domain.Product) (*domain.Product, error) {
    data := Product{}
    if err := copier.Copy(&data, product); err != nil {
        return nil, err
    }
    data.CreatedAt = time.Now()
    if err := repo.model.WithContext(ctx).Create(&data).Error; err != nil {
        return nil, err
    }

    product.Id = data.Id
    product.CreatedAt = data.CreatedAt
    return product, nil
}

func (repo *productRepo) List(ctx context.Context, req *domain.ListReq) (*domain.ListResp, error) {
    var data []*Product
    model := repo.model.WithContext(ctx)

    if req.Name != "" {
        model = model.Where("name like ?", fmt.Sprintf("%%%s%%", req.Name))
    }

    if len(req.Ids) > 0 {
        model = model.Where("id in ?", req.Ids)
    }

    var total int64
    if err := model.Count(&total).Error; err != nil {
        return nil, err
    }

    if req.Page > 0 && req.Limit > 0 {
        model = model.Scopes(paginate(req.Page, req.Limit))
    }

    if err := model.Find(&data).Error; err != nil {
        return nil, err
    }
    result := &domain.ListResp{
        Paginate: domain.Paginate{
            Total: uint(total),
            Page:  req.Page,
            Limit: req.Limit,
        },
    }
    products := make([]*domain.Product, 0, len(data))
    if err := copier.Copy(&products, data); err != nil {
        return nil, err
    }
    result.Data = products
    return result, nil
}

func (repo *productRepo) Detail(ctx context.Context, id uint) (*domain.Product, error) {
    var detail Product
    if err := repo.model.WithContext(ctx).Where("id = ?", id).First(&detail).Error; err != nil {
        return nil, err
    }
    product := &domain.Product{}
    if err := copier.Copy(product, detail); err != nil {
        return nil, err
    }
    return product, nil
}

func (repo *productRepo) Edit(ctx context.Context, id uint, product *domain.Product) (*domain.Product, error) {
    detail, err := repo.Detail(ctx, id)
    if err != nil {
        return nil, err
    }

    data := &domain.Product{}
    if err := copier.Copy(data, product); err != nil {
        return nil, err
    }

    result := repo.model.WithContext(ctx).Clauses(clause.Returning{}).Where("id = ?", id).Updates(data)
    if result.Error != nil {
        return nil, result.Error
    }

    if result.RowsAffected == 0 {
        return nil, errors.New("no data has been updated")
    }

    product.CreatedAt = detail.CreatedAt
    return product, nil
}

func (repo *productRepo) AddStock(ctx context.Context, id uint, num int) (*domain.Product, error) {
    detail, err := repo.Detail(ctx, id)
    if err != nil {
        return nil, err
    }
    if num == 0 {
        return detail, nil
    }

    result := repo.model.WithContext(ctx).Where("id = ?", id).Update("stock", gorm.Expr("stock + ?", num))
    if result.Error != nil {
        return nil, result.Error
    }
    if result.RowsAffected <= 0 {
        return nil, errors.New("no data has been updated")
    }

    if num < 0 {
        detail.Stock -= uint(math.Abs(float64(num)))
    } else {
        detail.Stock += uint(num)
    }

    product := &domain.Product{}
    if err := copier.Copy(product, detail); err != nil {
        return nil, err
    }
    return product, nil
}
